# package api

# import (
# 	"fmt"
# 	"net/http"
# 	"strings"

# 	"github.com/aptly-dev/aptly/aptly"
# 	"github.com/aptly-dev/aptly/deb"
# 	"github.com/aptly-dev/aptly/pgp"
# 	"github.com/aptly-dev/aptly/task"
# 	"github.com/aptly-dev/aptly/utils"
# 	"github.com/gin-gonic/gin"
# )

from context import context
import sys
from deb.publish import *

from typing import List,Dict,Tuple
pythonVersionInfo=sys.version_info
if pythonVersionInfo.minor>=8:
	from typing import TypedDict
else:
	from typing_extensions import TypedDict

from pgp.gnupg import *
# // SigningOptions is a shared between publish API GPG options structure
class SigningOptions(TypedDict):
	Skip           :bool
	GpgKey         :str
	Keyring        :str
	SecretKeyring  :str
	Passphrase     :str
	PassphraseFile :str


def getSigner(options :SigningOptions)->Tuple[GpgSigner,str]:
	if options.get('Skip') :
		return None, None
	
	aptlyContext=context.AptlyContext()
	signer :GpgSigner= aptlyContext.GetSigner()
	signer.SetKey(options.get('GpgKey',''))
	signer.SetKeyRing(options.get('Keyring',''), options.get('SecretKeyring',''))
	signer.SetPassphrase(options.get('Passphrase',''), options.get('PassphraseFile',''))
	signer.SetBatch(True)

	err = signer.Init()
	if err is not None :
		return None, err
	

	return signer, None


# // Replace '_' with '/' and double '__' with single '_'
def parseEscapedPath(path :str)-> str:
	result = path.replace( "_", "/").replace( "//", "_")
	if result == "" :
		result = "."
	
	return result


# // GET /publish
def apiPublishList(c :dict) :
	aptlyContext=context.AptlyContext()
	collectionFactory = aptlyContext.NewCollectionFactory()
	collection = collectionFactory.PublishedRepoCollection()

	result = []#make([]*deb.PublishedRepo, 0, collection.Len())
	def NoName(repo :PublishedRepo):
		collection.LoadComplete(repo, collectionFactory)	

		result.append( repo)

		return None
	collection.ForEach(NoName)

	# if err is not None {
	# 	c.AbortWithError(500, err)
	# 	return
	# }

	print(200, result)


class sourcesDict(TypedDict):
	Component:str
	Name:str
class publishRepoOrSnapshotPostData(TypedDict):
	SourceKind:str
	Sources:List[sourcesDict]
	Distribution         :str
	Label                :str
	Origin               :str
	NotAutomatic         :str
	ButAutomaticUpgrades :str
	ForceOverwrite       :bool
	SkipContents         :bool
	SkipBz2              :bool
	Architectures        :List[str]
	Signing              :SigningOptions
	AcquireByHash        :bool
# // POST /publish/:prefix
def apiPublishRepoOrSnapshot(c :dict) :
	param = parseEscapedPath(c.get("prefix"))
	storage, prefix = ParsePrefix(param)

	b:publishRepoOrSnapshotPostData=dict(
		SourceKind=c.get('SourceKind'),# `binding:"required"`
		Sources = c.get('Sources'), #`binding:"required"`
		Distribution         =c.get('Distribution'),
		Label                =c.get('Label'),
		Origin               =c.get('Origin'),
		NotAutomatic         =c.get('NotAutomatic'),
		ButAutomaticUpgrades =c.get('ButAutomaticUpgrades'),
		ForceOverwrite       =c.get('ForceOverwrite'),#bool
		SkipContents         =c.get('SkipContents'),#bool
		SkipBz2              =c.get('SkipBz2'),#bool
		Architectures        =c.get('Architectures',[]),
		Signing              =c.get('Signing',{}),
		AcquireByHash        =c.get('AcquireByHash'),#bool
	)

	# if c.Bind(&b) is not None {
	# 	return
	# }
	print('输入选项：',b)
	signer ,err= getSigner(b.get('Signing'))
	if err is not None :
		raise Exception("unable to initialize GPG signer: {}".format( err))
		
	
	if len(b.get('Sources')) == 0 :
		raise Exception("unable to publish: soures are empty")
		# return
	# }

	components =[]
	names= []#string
	sources =[]#interface{}
	resources= []#string
	aptlyContext=context.AptlyContext()
	collectionFactory = aptlyContext.NewCollectionFactory()

	if b.get('SourceKind') == "snapshot" :
		snapshot=None# *deb.Snapshot

		snapshotCollection = collectionFactory.SnapshotCollection()

		for  source in b.get('Sources') :
			components . append(  source.get('Component'))
			names. append( source.get('Name'))

			snapshot, err = snapshotCollection.ByName(source.get('Name'))
			if err is not None :
				# c.AbortWithError(404, fmt.Errorf("unable to publish: %s", err))
				return
			

			resources .append(   snapshot.ResourceKey())
			err = snapshotCollection.LoadComplete(snapshot)
			if err is not None :
				# c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
				return
			

			sources.append(  snapshot)
		
	elif b.get('SourceKind') ==  SourceLocalRepo :
		localRepo =None#*deb.LocalRepo

		localCollection = collectionFactory.LocalRepoCollection()

		for  source in b.get('Sources') :
			components . append(  source.get('Component'))
			names. append( source.get('Name'))

			localRepo, err = localCollection.ByName(source.get('Name'))
			if err is not None :
				raise Exception("unable to publish: {}".format(err))
				
			# }

			resources.append(localRepo.Key())
			_,err = localCollection.LoadComplete(localRepo)
			if err is not None :
				raise Exception("unable to publish: {}".format(err))
				# c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
			# }

			sources .append( localRepo)
		# }
	else :
		# c.AbortWithError(400, fmt.Errorf("unknown SourceKind"))
		raise Exception("unknown SourceKind")
		
	# }
	published, err =  NewPublishedRepo(storage, prefix, b.get('Distribution'), b.get('Architectures'), components, sources, collectionFactory)
	if err is not None :
		# c.AbortWithError(500, fmt.Errorf("unable to publish: %s", err))
		return
	# }
	resources .append(published.Key())
	collection = collectionFactory.PublishedRepoCollection()

	taskName = "Publish {}: {}".format(b.get('SourceKind'),  ", ".join(names))

	# taskDetail = task.PublishDetail{
	# 		Detail: detail,
	# 	}
	# 	publishOutput = &task.PublishOutput{
	# 		Progress:      out,
	# 		PublishDetail: taskDetail,
	# 	}
	publishOutput=None
	if b.get('Origin') != "" :
		published.Origin = b.get('Origin')
	
	if b.get('NotAutomatic') != "" :
		published.NotAutomatic = b.get('NotAutomatic')
	
	if b.get('ButAutomaticUpgrades') != "" :
		published.ButAutomaticUpgrades = b.get('ButAutomaticUpgrades')
	
	published.Label = b.get('Label')

	published.SkipContents = aptlyContext.Config().SkipContentsPublishing
	if b.get('SkipContents') is not None :
		published.SkipContents = b.get('SkipContents')
	

	published.SkipBz2 = aptlyContext.Config().SkipBz2Publishing
	if b.get('SkipBz2') is not None :
		published.SkipBz2 = b.get('SkipBz2')
	

	if b.get('AcquireByHash') is not None :
		published.AcquireByHash = b.get('AcquireByHash')
	

	duplicate = collection.CheckDuplicate(published)
	if duplicate is not None :
		collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
		return #&task.ProcessReturnValue{Code: http.StatusBadRequest, Value: None}, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
	

	err = published.Publish(aptlyContext.PackagePool(), aptlyContext, collectionFactory, signer, publishOutput, b.get('ForceOverwrite'))
	if err is not None :
		return #&task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: None}, fmt.Errorf("unable to publish: %s", err)
	

	err = collection.Add(published)
	# if err is not None {
	# 	return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: None}, fmt.Errorf("unable to save to DB: %s", err)
	# }

	# return &task.ProcessReturnValue{Code: http.StatusCreated, Value: published}, None
	# maybeRunTaskInBackground(c, taskName, resources, def(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
	# 	taskDetail = task.PublishDetail{
	# 		Detail: detail,
	# 	}
	# 	publishOutput = &task.PublishOutput{
	# 		Progress:      out,
	# 		PublishDetail: taskDetail,
	# 	}

	# 	if b.Origin != "" {
	# 		published.Origin = b.Origin
	# 	}
	# 	if b.NotAutomatic != "" {
	# 		published.NotAutomatic = b.NotAutomatic
	# 	}
	# 	if b.ButAutomaticUpgrades != "" {
	# 		published.ButAutomaticUpgrades = b.ButAutomaticUpgrades
	# 	}
	# 	published.Label = b.Label

	# 	published.SkipContents = context.Config().SkipContentsPublishing
	# 	if b.SkipContents is not None {
	# 		published.SkipContents = *b.SkipContents
	# 	}

	# 	published.SkipBz2 = context.Config().SkipBz2Publishing
	# 	if b.SkipBz2 is not None {
	# 		published.SkipBz2 = *b.SkipBz2
	# 	}

	# 	if b.AcquireByHash is not None {
	# 		published.AcquireByHash = *b.AcquireByHash
	# 	}

	# 	duplicate = collection.CheckDuplicate(published)
	# 	if duplicate is not None {
	# 		collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
	# 		return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: None}, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
	# 	}

	# 	err = published.Publish(context.PackagePool(), context, collectionFactory, signer, publishOutput, b.ForceOverwrite)
	# 	if err is not None {
	# 		return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: None}, fmt.Errorf("unable to publish: %s", err)
	# 	}

	# 	err = collection.Add(published)
	# 	if err is not None {
	# 		return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: None}, fmt.Errorf("unable to save to DB: %s", err)
	# 	}

	# 	return &task.ProcessReturnValue{Code: http.StatusCreated, Value: published}, None
	# })


# // PUT /publish/:prefix/:distribution
# def apiPublishUpdateSwitch(c :dict) {
# 	param := parseEscapedPath(c.Params.ByName("prefix"))
# 	storage, prefix := deb.ParsePrefix(param)
# 	distribution := c.Params.ByName("distribution")

# 	var b struct {
# 		ForceOverwrite bool
# 		Signing        SigningOptions
# 		SkipContents   *bool
# 		SkipBz2        *bool
# 		SkipCleanup    *bool
# 		Snapshots      []struct {
# 			Component string `binding:"required"`
# 			Name      string `binding:"required"`
# 		}
# 		AcquireByHash *bool
# 	}

# 	if c.Bind(&b) is not None  {
# 		return
# 	}

# 	signer, err := getSigner(&b.Signing)
# 	if err is not None  {
# 		AbortWithJSONError(c, 500, fmt.Errorf("unable to initialize GPG signer: %s", err))
# 		return
# 	}

# 	collectionFactory := context.NewCollectionFactory()
# 	collection := collectionFactory.PublishedRepoCollection()

# 	published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
# 	if err is not None  {
# 		AbortWithJSONError(c, 404, fmt.Errorf("unable to update: %s", err))
# 		return
# 	}
# 	err = collection.LoadComplete(published, collectionFactory)
# 	if err is not None  {
# 		AbortWithJSONError(c, 500, fmt.Errorf("unable to update: %s", err))
# 		return
# 	}

# 	var updatedComponents []string
# 	var updatedSnapshots []string
# 	var resources []string

# 	if published.SourceKind == deb.SourceLocalRepo {
# 		if len(b.Snapshots) > 0 {
# 			AbortWithJSONError(c, 400, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
# 			return
# 		}
# 		updatedComponents = published.Components()
# 		for _, component := range updatedComponents {
# 			published.UpdateLocalRepo(component)
# 		}
# 	} else if published.SourceKind == "snapshot" {
# 		publishedComponents := published.Components()
# 		for _, snapshotInfo := range b.Snapshots {
# 			if !utils.StrSliceHasItem(publishedComponents, snapshotInfo.Component) {
# 				AbortWithJSONError(c, 404, fmt.Errorf("component %s is not in published repository", snapshotInfo.Component))
# 				return
# 			}

# 			snapshotCollection := collectionFactory.SnapshotCollection()
# 			snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name)
# 			if err2 is not None  {
# 				AbortWithJSONError(c, 404, err2)
# 				return
# 			}

# 			err2 = snapshotCollection.LoadComplete(snapshot)
# 			if err2 is not None  {
# 				AbortWithJSONError(c, 500, err2)
# 				return
# 			}

# 			published.UpdateSnapshot(snapshotInfo.Component, snapshot)
# 			updatedComponents = append(updatedComponents, snapshotInfo.Component)
# 			updatedSnapshots = append(updatedSnapshots, snapshot.Name)
# 		}
# 	} else {
# 		AbortWithJSONError(c, 500, fmt.Errorf("unknown published repository type"))
# 		return
# 	}

# 	if b.SkipContents is not None  {
# 		published.SkipContents = *b.SkipContents
# 	}

# 	if b.SkipBz2 is not None  {
# 		published.SkipBz2 = *b.SkipBz2
# 	}

# 	if b.AcquireByHash is not None  {
# 		published.AcquireByHash = *b.AcquireByHash
# 	}

# 	resources = append(resources, string(published.Key()))
# 	taskName := fmt.Sprintf("Update published %s (%s): %s", published.SourceKind, strings.Join(updatedComponents, " "), strings.Join(updatedSnapshots, ", "))
# 	maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
# 		err := published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite)
# 		if err is not None  {
# 			return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value:   None }, fmt.Errorf("unable to update: %s", err)
# 		}

# 		err = collection.Update(published)
# 		if err is not None  {
# 			return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value:   None }, fmt.Errorf("unable to save to DB: %s", err)
# 		}

# 		if b.SkipCleanup is  None  || !*b.SkipCleanup {
# 			err = collection.CleanupPrefixComponentFiles(published.Prefix, updatedComponents,
# 				context.GetPublishedStorage(storage), collectionFactory, out)
# 			if err is not None  {
# 				return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value:   None }, fmt.Errorf("unable to update: %s", err)
# 			}
# 		}

# 		return &task.ProcessReturnValue{Code: http.StatusOK, Value: published},   None 
# 	})
# }

# // DELETE /publish/:prefix/:distribution
def apiPublishDrop(c :dict) :
	force = c.get("force") == "1"
	skipCleanup = c.get("SkipCleanup") == "1"

	param = parseEscapedPath(c.get("prefix"))
	storage, prefix = ParsePrefix(param)
	distribution = c.get("distribution")

	collectionFactory = context.NewCollectionFactory()
	collection = collectionFactory.PublishedRepoCollection()

	published, err = collection.ByStoragePrefixDistribution(storage, prefix, distribution)
	# if err is not None  {
	# 	AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to drop: %s", err))
	# 	return
	# }

	resources = str(published.Key())

	taskName = "Delete published {} ({})".format( prefix, distribution)
	# maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
	err = collection.Remove(context, storage, prefix, distribution,
		collectionFactory, '', force, skipCleanup)
	print(err)
		# if err is not None  {
		# 	return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value:   None }, fmt.Errorf("unable to drop: %s", err)
		# }

		# return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}},   None 

